﻿using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Bridge.Shared.Redis
{
    public partial class RedisManager: ICacheManager
    {
        private bool _disposed;
        private IDatabase _db;
        private readonly IRedisConnectionWrapper _connectionWrapper;
        private readonly RedisConfiguration _redisConfiguration;

        public RedisManager(IRedisConnectionWrapper connectionWrapper, IOptions<RedisConfiguration> options)
        {
            _connectionWrapper = connectionWrapper;
            _redisConfiguration = options.Value;
            _db = _connectionWrapper.GetDatabase(_redisConfiguration.DatabaseId);
        }


        #region 公用
        private async Task<IDatabase> GetDatabaseAsync()
        {
            if (_db != null)
                return _db;

            _db = await _connectionWrapper.GetDatabaseAsync(_redisConfiguration.DatabaseId);

            return _db;
        }


        /// <summary>
        /// Redis锁
        /// </summary>
        /// <param name="key"></param>
        /// <param name="token"></param>
        /// <param name="span"></param>
        /// <returns></returns>
        public async Task<T> LockQueryAsync<T>(string key)
        {
            return (await _db.LockQueryAsync(key)).DeserializeObject<T>();
        }

        /// <summary>
        /// Redis锁
        /// </summary>
        /// <param name="key"></param>
        /// <param name="token"></param>
        /// <param name="span"></param>
        /// <returns></returns>
        public async Task<bool> LockTakeAsync(string key, RedisValue token, TimeSpan span)
        {
            return await _db.LockTakeAsync(key, token, span);
        }

        /// <summary>
        /// 释放锁
        /// </summary>
        /// <param name="key"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public async Task<bool> ReleaseTakeAsync(string key, RedisValue token)
        {
            return await _db.LockReleaseAsync(key, token);
        }

        /// <summary>
        /// 添加 Key 的前缀
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private string AddKeyPrefix(string key)
        {
            return $"{_redisConfiguration.RedisDataProtectionKey}:{key}";
        }
        #endregion

        #region Hash 操作
        /// <summary>
        /// 判断该字段是否存在 hash 中
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <returns></returns>
        public virtual async Task<bool> HashExistsAsync(string redisKey, string hashField)
        {
            redisKey = AddKeyPrefix(redisKey);
            return (await GetDatabaseAsync()).HashExists(redisKey, hashField);
        }

        /// <summary>
        /// 从 hash 中移除指定字段
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <returns></returns>
        public virtual async Task<bool> HashDeleteAsync(string redisKey, string hashField)
        {
            redisKey = AddKeyPrefix(redisKey);
            return (await GetDatabaseAsync()).HashDelete(redisKey, hashField);
        }
        /// <summary>
        /// 从 hash 中移除指定字段
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <returns></returns>
        public virtual async Task<long> HashDeleteAsync(string redisKey, IEnumerable<RedisValue> hashField)
        {
            redisKey = AddKeyPrefix(redisKey);
            return (await GetDatabaseAsync()).HashDelete(redisKey, hashField.ToArray());
        }

        /// <summary>
        /// 在 hash 设定值（序列化）
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <param name="value"></param>
        /// <param name="expireTime">过期时间 分钟 默认五分钟</param>
        /// <returns></returns>
        public virtual async Task<bool> HashSetAsync<T>(string redisKey, string hashField, T value, int expireTime = 5)
        {
            var db = await GetDatabaseAsync();

            redisKey = AddKeyPrefix(redisKey);
            var setting = new JsonSerializerSettings();
            setting.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
            var json = JsonConvert.SerializeObject(value, setting);
            var result = db.HashSet(redisKey, hashField, json);
            await db.KeyExpireAsync(redisKey, DateTime.Now.AddMinutes(expireTime));
            return result;
        }

        /// <summary>
        /// 在 hash 中获取值（反序列化）
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <returns></returns>
        public virtual async Task<T> HashGetAsync<T>(string redisKey, string hashField, bool prefix = true)
        {
            redisKey = prefix ? AddKeyPrefix(redisKey) : redisKey;
            var result = (await GetDatabaseAsync()).HashGet(redisKey, hashField);
            return result.HasValue ? JsonConvert.DeserializeObject<T>(result) : default(T);
        }

        public virtual async Task<List<T>> HashGetAsync<T>(string redisKey, List<string> hashFields, bool prefix = true)
        {
            List<T> list = new List<T>();

            List<RedisValue> redisValues = new List<RedisValue>();
            hashFields.ForEach(m => redisValues.Add(m));
            redisKey = prefix ? AddKeyPrefix(redisKey) : redisKey;
            var hashVals = (await GetDatabaseAsync()).HashGet(redisKey, redisValues.ToArray());
            hashVals = hashVals.Where(m => m.HasValue).ToArray();
            foreach (var item in hashVals)
            {
                list.Add(JsonConvert.DeserializeObject<T>(item));
            }
            return list;
        }

        /// <summary>
        ///  获取key中所有field的值。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="redisKey"></param>
        /// <param name="flags"></param>
        /// <returns></returns>
        public virtual async Task<List<T>> HashGetAllValuesAsync<T>(string redisKey, CommandFlags flags = CommandFlags.None)
        {
            List<T> list = new List<T>();
            redisKey = AddKeyPrefix(redisKey);
            var hashVals = (await GetDatabaseAsync()).HashValues(redisKey, flags).ToList();
            foreach (var item in hashVals)
            {
                list.Add(JsonConvert.DeserializeObject<T>(item));
            }
            return list;
        }
        #endregion Hash 操作

        #region Strng操作

        /// <summary>
        /// String 写
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <returns></returns>
        public virtual async Task<bool> StringSet(string redisKey, string value, int expireTime = 5)
        {
            var db = (await GetDatabaseAsync());
            var ts = DateTime.Now.AddMinutes(expireTime) - DateTime.Now;
            redisKey = AddKeyPrefix(redisKey);
            var result = await db.StringSetAsync(redisKey, value, ts);
            //await db.KeyExpireAsync(redisKey, DateTime.Now.AddMinutes(expireTime));
            return result;
        }
        /// <summary>
        /// String 读
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <returns></returns>
        public virtual async Task<string> StringGet(string redisKey)
        {
            redisKey = AddKeyPrefix(redisKey);

            var value = await (await GetDatabaseAsync()).StringGetAsync(redisKey);
            return value;
        }
        #endregion

        #region 原子递增
        public virtual async Task<long> IncrementAsync(string redisKey, long value = 1, CommandFlags flags = CommandFlags.None)
        {
            if (redisKey == null)
            {
                throw new ArgumentNullException(nameof(redisKey));
            }

            redisKey = AddKeyPrefix(redisKey);

            return await (await GetDatabaseAsync()).StringIncrementAsync(redisKey, value, flags);
        }

        public virtual async Task<long> IncrementAsyncWithTime(string redisKey, long value = 1, CommandFlags flags = CommandFlags.None, int expireTime = 5)
        {
            if (redisKey == null)
            {
                throw new ArgumentNullException(nameof(redisKey));
            }

            redisKey = AddKeyPrefix(redisKey);
            var db = await GetDatabaseAsync();

            var result = await db.StringIncrementAsync(redisKey, value, flags);
            var ts = DateTime.Now.AddMinutes(expireTime) - DateTime.Now;
            redisKey = AddKeyPrefix(redisKey);
            await db.KeyExpireAsync(redisKey, ts);
            return result;
        }

        #endregion

        #region 有效期
        public async Task<bool> KeyExpireAsync(string redisKey, TimeSpan? expiry, CommandFlags flags = CommandFlags.None)
        {
            if (redisKey == null)
            {
                throw new ArgumentNullException(nameof(redisKey));
            }
            if (expiry == null)
            {
                throw new ArgumentNullException(nameof(expiry));
            }

            redisKey = AddKeyPrefix(redisKey);

            return await (await GetDatabaseAsync()).KeyExpireAsync(redisKey, expiry, flags);
        }
        #endregion

        /// <summary>
        /// 移除Key
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="flags"></param>
        /// <returns></returns>
        public async Task<bool> RemoveKeyAsync(string redisKey, CommandFlags flags = CommandFlags.None)
        {
            if (redisKey == null)
            {
                throw new ArgumentNullException(nameof(redisKey));
            }

            redisKey = AddKeyPrefix(redisKey);

            return await (await GetDatabaseAsync()).KeyDeleteAsync(redisKey, flags);
        }


        #region 对象操作
        public Task<bool> AddAsync<T>(string key, T value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None, CancellationToken token = default)
        {
            return StringSetAsync(key, JsonConvert.SerializeObject(value), expiry, when, flags, token);
        }

        public async Task<bool> StringSetAsync(string redisKey, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None, CancellationToken token = default)
        {
            if (redisKey == null)
            {
                throw new ArgumentNullException(nameof(redisKey));
            }

            token.ThrowIfCancellationRequested();

            redisKey = AddKeyPrefix(redisKey);

            return await (await GetDatabaseAsync()).StringSetAsync(redisKey, value, expiry, when, flags);
        }

        public async Task<T> GetAsync<T>(string key, CommandFlags flags = CommandFlags.None, CancellationToken token = default)
        {
            return (await StringGetAsync(key, flags, token)).DeserializeObject<T>();
        }

        public async Task<RedisValue> StringGetAsync(string redisKey, CommandFlags flags = CommandFlags.None, CancellationToken token = default)
        {
            if (redisKey == null)
            {
                throw new ArgumentNullException(nameof(redisKey));
            }
            token.ThrowIfCancellationRequested();
            redisKey = AddKeyPrefix(redisKey);

            return await (await GetDatabaseAsync()).StringGetAsync(redisKey, flags);
        }
        #endregion

        #region 队列
        /// <summary>
        /// 入队
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="redisKey"></param>
        /// <param name="value"></param>
        /// <param name="flags"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public async Task<long> EnqueueList<T>(string redisKey, T value, CommandFlags flags = CommandFlags.None, CancellationToken token = default)
        {
            if (redisKey == null)
            {
                throw new ArgumentNullException(nameof(redisKey));
            }

            token.ThrowIfCancellationRequested();

            redisKey = AddKeyPrefix(redisKey);

            var setting = new JsonSerializerSettings();
            setting.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
            var json = JsonConvert.SerializeObject(value, setting);

            return await (await GetDatabaseAsync()).ListRightPushAsync(redisKey, json, When.Always, flags);
        }

        /// <summary>
        /// 出队列
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="redisKey"></param>
        /// <param name="value"></param>
        /// <param name="flags"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public async Task<T> DequeueList<T>(string redisKey, long index, CommandFlags flags = CommandFlags.None, CancellationToken token = default)
        {
            if (redisKey == null)
            {
                throw new ArgumentNullException(nameof(redisKey));
            }

            token.ThrowIfCancellationRequested();

            redisKey = AddKeyPrefix(redisKey);

            var result = await (await GetDatabaseAsync()).ListLeftPopAsync(redisKey, flags);

            return result.HasValue ? JsonConvert.DeserializeObject<T>(result) : default(T);
        }
        #endregion



        /// <summary>
        /// 释放
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;

            _disposed = true;
        }
    }
}
